package prometheus

import (
	"fmt"
	"net/http"
	"sync"
	"time"

	"gitee.com/zawei/yggdrasil/pkg/config"
	"gitee.com/zawei/yggdrasil/pkg/log"
	"gitee.com/zawei/yggdrasil/pkg/metrics"
	"gitee.com/zawei/yggdrasil/pkg/transport/server/governor"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

const DefaultFlushInterval = 10 * time.Second

var (
	onceEnable         sync.Once
	prometheusRegistry = prometheus.NewRegistry()
)

var _ metrics.Registry = (*registry)(nil)

type Config struct {
	EnableRunTimeMetrics bool
	FlushInterval        time.Duration
}

func (c *Config) Build() *registry {
	if c.FlushInterval == 0 {
		c.FlushInterval = DefaultFlushInterval
	}
	if c.EnableRunTimeMetrics {
		onceEnable.Do(func() {
			prometheusRegistry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
			prometheusRegistry.MustRegister(prometheus.NewGoCollector())
		})
	}
	return &registry{
		Config:     c,
		lc:         sync.RWMutex{},
		lg:         sync.RWMutex{},
		ls:         sync.RWMutex{},
		summaries:  make(map[string]*prometheus.SummaryVec),
		counters:   make(map[string]*prometheus.CounterVec),
		gauges:     make(map[string]*prometheus.GaugeVec),
		histograms: make(map[string]*prometheus.HistogramVec),
	}
}

type registry struct {
	*Config
	lc         sync.RWMutex
	lg         sync.RWMutex
	ls         sync.RWMutex
	counters   map[string]*prometheus.CounterVec
	gauges     map[string]*prometheus.GaugeVec
	summaries  map[string]*prometheus.SummaryVec
	histograms map[string]*prometheus.HistogramVec
}

//CreateGauge create collector
func (c *registry) CreateGauge(opts metrics.GaugeOpts) error {
	c.lg.RLock()
	_, ok := c.gauges[opts.Name]
	c.lg.RUnlock()
	if ok {
		return fmt.Errorf("metric [%s] is duplicated", opts.Name)
	}
	c.lg.Lock()
	defer c.lg.Unlock()
	gVec := prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: opts.Namespace,
		Subsystem: opts.Subsystem,
		Name:      opts.Name,
		Help:      opts.Help,
	}, opts.Labels)
	c.gauges[opts.Name] = gVec

	prometheusRegistry.MustRegister(gVec)
	return nil
}

//GaugeSet set value
func (c *registry) GaugeSet(name string, val float64, labels map[string]string) error {
	c.lg.RLock()
	gVec, ok := c.gauges[name]
	c.lg.RUnlock()
	if ok {
		return nil
	}
	gVec.With(labels).Set(val)
	return nil
}

//CreateCounter create collector
func (c *registry) CreateCounter(opts metrics.CounterOpts) error {
	c.lc.RLock()
	_, ok := c.counters[opts.Name]
	c.lc.RUnlock()
	if ok {
		return fmt.Errorf("metric [%s] is duplicated", opts.Name)
	}
	c.lc.Lock()
	defer c.lc.Unlock()
	v := prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: opts.Namespace,
		Subsystem: opts.Subsystem,
		Name:      opts.Name,
		Help:      opts.Help,
	}, opts.Labels)
	c.counters[opts.Name] = v
	prometheusRegistry.MustRegister(v)
	return nil
}

//CounterAdd increase value
func (c *registry) CounterAdd(name string, val float64, labels map[string]string) error {
	c.lc.RLock()
	v, ok := c.counters[name]
	c.lc.RUnlock()
	if !ok {
		return fmt.Errorf("metrics do not exists, create it first")
	}
	v.With(labels).Add(val)
	return nil
}

//CreateSummary create collector
func (c *registry) CreateSummary(opts metrics.SummaryOpts) error {
	c.ls.RLock()
	_, ok := c.summaries[opts.Name]
	c.ls.RUnlock()
	if ok {
		return fmt.Errorf("metric [%s] is duplicated", opts.Name)
	}
	c.ls.Lock()
	defer c.ls.Unlock()
	v := prometheus.NewSummaryVec(prometheus.SummaryOpts{
		Namespace:  opts.Namespace,
		Subsystem:  opts.Subsystem,
		Name:       opts.Name,
		Help:       opts.Help,
		Objectives: opts.Objectives,
	}, opts.Labels)
	c.summaries[opts.Name] = v
	prometheusRegistry.MustRegister(v)
	return nil
}

//SummaryObserve set value
func (c *registry) SummaryObserve(name string, val float64, labels map[string]string) error {
	c.ls.RLock()
	v, ok := c.summaries[name]
	c.ls.RUnlock()
	if !ok {
		return fmt.Errorf("metrics do not exists, create it first")
	}
	v.With(labels).Observe(val)
	return nil
}

//CreateHistogram create collector
func (c *registry) CreateHistogram(opts metrics.HistogramOpts) error {
	c.ls.RLock()
	_, ok := c.histograms[opts.Name]
	c.ls.RUnlock()
	if ok {
		return fmt.Errorf("metric [%s] is duplicated", opts.Name)
	}
	c.ls.Lock()
	defer c.ls.Unlock()
	v := prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Namespace: opts.Namespace,
		Subsystem: opts.Subsystem,
		Name:      opts.Name,
		Help:      opts.Help,
		Buckets:   opts.Buckets,
	}, opts.Labels)
	c.histograms[opts.Name] = v
	prometheusRegistry.MustRegister(v)
	return nil
}

//HistogramObserve set value
func (c *registry) HistogramObserve(name string, val float64, labels map[string]string) error {
	c.ls.RLock()
	v, ok := c.histograms[name]
	c.ls.RUnlock()
	if !ok {
		return fmt.Errorf("metrics do not exists, create it first")
	}
	v.With(labels).Observe(val)
	return nil
}

func init() {
	governor.HandleFunc("/metrics/prometheus", func(w http.ResponseWriter, r *http.Request) {
		promhttp.Handler().ServeHTTP(w, r)
	})
}

func NewMetricsRegistry() metrics.Registry {
	c := &Config{}
	if err := config.Get("metric.prometheus").Scan(c); err != nil {
		log.Fatalw("fault to scan prometheus metric config", "err", err)
	}
	return c.Build()
}
